﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Event.EventBus
{
    public class Messenger:IMessenger
    {
        private static readonly object CreationLock = new object();
        private static IMessenger _defaultInstance;
        private readonly object _registerLock = new object();
        private Dictionary<Type, List<WeakAction>> _recipientsStrictAction;
        private bool _isCleanupRegistered;

        public static IMessenger Default
        {
            get
            {
                if (_defaultInstance == null)
                {
                    lock (CreationLock)
                    {
                        if (_defaultInstance == null)
                        {
                            _defaultInstance = new Messenger();
                        }
                    }
                }

                return _defaultInstance;
            }
        }

        public void Register<TMessage>(object recipient, Action<TMessage> action)
        {
            lock (_registerLock)
            {
                var messageType = typeof(TMessage);

                Dictionary<Type, List<WeakAction>> recipients;
               
                    if (_recipientsStrictAction == null)
                    {
                        _recipientsStrictAction = new Dictionary<Type, List<WeakAction>>();
                    }

                    recipients = _recipientsStrictAction;

                lock (recipients)
                {
                    List<WeakAction> list;

                    if (!recipients.ContainsKey(messageType))
                    {
                        list = new List<WeakAction>();
                        recipients.Add(messageType, list);
                    }
                    else
                    {
                        list = recipients[messageType];
                    }

                    var weakAction = new WeakAction<TMessage>(recipient, action);
                   
                    list.Add(weakAction);
                }
            }

            RequestCleanup();
        }


        public void Send<TMessage>(TMessage message)
        {
            var messageType = typeof(TMessage);

            if (_recipientsStrictAction != null)
            {
                List<WeakAction> list = null;

                lock (_recipientsStrictAction)
                {
                    if (_recipientsStrictAction.ContainsKey(messageType))
                    {
                        list = _recipientsStrictAction[messageType]
                            .Take(_recipientsStrictAction[messageType].Count())
                            .ToList();
                    }
                }

                if (list != null)
                {
                    SendToList(message, list);
                }
            }

            RequestCleanup();
        }

        private static void SendToList<TMessage>(
           TMessage message,
           IEnumerable<WeakAction> weakActions)
        {
            if (weakActions != null)
            {
                var list = weakActions.ToList();
                var listClone = list.Take(list.Count()).ToList();

                foreach (var item in listClone)
                {
                    var executeAction = item as IExecuteWithObject;

                    if (executeAction != null&& item.IsAlive&& item.Target != null)
                    {
                        executeAction.ExecuteWithObject(message);
                    }
                }
            }
        }

        public void RequestCleanup()
        {
            if (!_isCleanupRegistered)
            {
                Cleanup();
                _isCleanupRegistered = true;
            }
        }

        public void Cleanup()
        {
            CleanupList(_recipientsStrictAction);
            _isCleanupRegistered = false;
        }

        private static void CleanupList(IDictionary<Type, List<WeakAction>> lists)
        {
            if (lists == null)
            {
                return;
            }

            lock (lists)
            {
                var listsToRemove = new List<Type>();
                foreach (var list in lists)
                {
                    var recipientsToRemove = list.Value
                        .Where(item => item == null || !item.IsAlive)
                        .ToList();

                    foreach (var recipient in recipientsToRemove)
                    {
                        list.Value.Remove(recipient);
                    }

                    if (list.Value.Count == 0)
                    {
                        listsToRemove.Add(list.Key);
                    }
                }

                foreach (var key in listsToRemove)
                {
                    lists.Remove(key);
                }
            }
        }

    }
}